﻿using Infrastructure.Attribute;
using Org.BouncyCastle.Utilities.Net;
using S7.Net;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Timers;
using ZR.DataCollection.Helper;
using ZR.DataCollection.ICommunication;
using ZR.DataCollection.Model;

namespace ZR.DataCollection.Communication
{
    [AppService(ServiceType = typeof(INormCommunication), ServiceLifetime = LifeTime.Singleton)]
    public class SieminCommunication : INormCommunication
    {
        public event Action<int, string, object> plc_dataChange;
        /// <summary>
        /// 块地址 - 长度
        /// </summary>
        private Dictionary<int, int> _dbPieceAL;
        private List<CommPoint> _ponits;
        /// <summary>
        /// 点位 - 值
        /// </summary>
        private ConcurrentDictionary<string, object> keyValuePairs;
        /// <summary>
        /// 块地址 - 通信点位集合
        /// </summary>
        private Dictionary<int, List<CommPoint>> KeyValuePairGroupByBlock;
        private System.Threading.Timer timer;
        private Plc siemenPlc;

        /// <summary>
        /// PLC连接
        /// </summary>
        /// <param name="ip">IP地址</param>
        /// <param name="port">端口号</param>
        /// <param name="rack">机架号</param>
        /// <param name="slot">插槽号</param>
        /// <param name="cpuType">PLC型号</param>
        public void Connection(string ip, int port, int rack, int slot, CpuType cpuType = CpuType.S71500)
        {
            if (siemenPlc == null)
            {
                siemenPlc = new Plc((CpuType)cpuType, ip, (short)rack, (short)slot);
                siemenPlc.ReadTimeout = 2000;
                siemenPlc.WriteTimeout = 2000;
            }
        }

        /// <summary>
        /// 启动PLC通讯
        /// </summary>
        /// <returns></returns>
        public bool Start()
        {
            try
            {
                siemenPlc.Open();
                if (siemenPlc.IsConnected)
                {
                    if (timer == null)
                    {
                        timer = new System.Threading.Timer(Timer_Elapsed, "State Info", 2000, 500);
                    }
                    return true;
                }
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        /// <summary>
        /// 关闭PLC通讯
        /// </summary>
        /// <returns></returns>
        public bool Close()
        {
            timer.Change(Timeout.Infinite, Timeout.Infinite);
            timer.Dispose();
            siemenPlc.Close();
            return true;
        }

        /// <summary>
        /// 点位监控
        /// </summary>
        /// <param name="ponits">点位集合</param>
        /// <param name="keyValues">地址，长度集合</param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public bool ConnectionSubPonit(List<CommPoint> ponits, Dictionary<int, int> keyValues)
        {
            if (siemenPlc != null)
            {
                siemenPlc.Close();
            }
            if (timer != null)
            {
                timer.Change(Timeout.Infinite, Timeout.Infinite);
            }
            keyValuePairs = null;
            KeyValuePairGroupByBlock = null;
            _ponits = null;
            _dbPieceAL = null;
            _dbPieceAL = keyValues;
            _ponits = ponits;
            KeyValuePairGroupByBlock = new Dictionary<int, List<CommPoint>>();
            foreach (var item in keyValues)
            {
                // db块地址、这个地址的所有点位信息
                KeyValuePairGroupByBlock.Add(item.Key, _ponits.OrderBy(obd => obd.SubItem).Where(w => w.BlockAddress.Equals(item.Key)).ToList());
            }
            keyValuePairs = new ConcurrentDictionary<string, object>();
            HashSet<Tuple<int, string>> isRepeat = new HashSet<Tuple<int, string>>();
            bool has = ponits.Any(a => !isRepeat.Add(Tuple.Create(a.ItemNo, a.ItemAddress)));
            if (has)
            {
                throw new Exception("添加了重复的ItemNo或者ItemAddress");
            }
            foreach (var commPonit in ponits)
            {
                keyValuePairs[commPonit.ItemAddress] = null;
            }

            _ponits = ponits;
            return true;
        }

        /// <summary>
        /// 释放连接
        /// </summary>
        public void DisConnection()
        {
            Close();
        }

        /// <summary>
        /// 释放(Using)
        /// </summary>
        public void Dispose()
        {
            DisConnection();
            siemenPlc = null;
        }

        private void Timer_Elapsed(object sender)
        {
            _ = CheckAndProcessAsync();
        }

        private SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);

        private async Task CheckAndProcessAsync()
        {
            await _semaphore.WaitAsync();

            try
            {
                //块地址 - 读取内容,该变量已经去重复，
                var pairs = new Dictionary<int, Task<byte[]>>();
                foreach (var item in _dbPieceAL)
                {
                    //pairs[item.Key] = Task.Run(() => siemenPlc.ReadBytesAsync(DataType.DataBlock, item.Key, 0, item.Value));
                    pairs[item.Key] = siemenPlc.ReadBytesAsync(DataType.DataBlock, item.Key, 0, item.Value);

                }
                var results = await Task.WhenAll(pairs.Values);
                for (int i = 0; i < pairs.Count; i++)
                {
                    int key = pairs.ElementAt(i).Key;
                    byte[] resultBytes = results[i];

                    foreach (var commPoint in KeyValuePairGroupByBlock[key])
                    {
                        var price = AddressResult(commPoint.ItemType, commPoint.ItemAddress, resultBytes);

                        if (price.ToString() != keyValuePairs.GetValueOrDefault(commPoint.ItemAddress)?.ToString())
                        {
                            //判断值不相等，说明订阅数据发生变化。

                            lock (keyValuePairs)
                            {
                                keyValuePairs[commPoint.ItemAddress] = price;
                            }

                            if (commPoint.SubItem)
                            {
                               

                                plc_dataChange?.Invoke(commPoint.ItemNo, commPoint.ItemAddress, price);
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
            finally
            {
                _semaphore.Release();
            }
        }

        public void HandInvoke(int itemNo, string address, object value)
        {
            plc_dataChange?.Invoke(itemNo, address, value);
        }

        /// <summary>
        /// 数据获取
        /// </summary>
        /// <param name="addressType">点位类型 eg:string、int16...</param>
        /// <param name="address">绝对地址 eg:DB3010.DBW2004 </param>
        /// <param name="bytes">数组结果</param>
        /// <returns></returns>
        private object AddressResult(string addressType, string address, byte[] bytes)
        {
            switch (addressType.ToLower())
            {
                case "string":
                    string patternString = @"\.DBB(\d+)\.(\d+)";
                    Match matchString = Regex.Match(address, patternString);
                    if (matchString.Success)
                    {
                        int numberString = int.Parse(matchString.Groups[1].Value);
                        int lengthString = int.Parse(matchString.Groups[2].Value);

                        return DataConversion.GetStringDataByBytes(bytes, numberString, lengthString);
                    }
                    return null;

                case "int16":
                    string patternInt16 = @"\.DBW(\d+)";
                    Match matchInt16 = Regex.Match(address, patternInt16);
                    if (matchInt16.Success)
                    {
                        int numberInt16 = int.Parse(matchInt16.Groups[1].Value);
                        return DataConversion.GetInt16DataByBytes(bytes, numberInt16);
                    }
                    return null;

                case "int32":
                    string patternInt32 = @"\.DBD(\d+)";
                    Match matchInt32 = Regex.Match(address, patternInt32);
                    if (matchInt32.Success)
                    {
                        int numberInt32 = int.Parse(matchInt32.Groups[1].Value);
                        return DataConversion.GetInt32DataByBytes(bytes, numberInt32);
                    }
                    return null;

                case "double":
                    string patternDouble = @"\.DBD(\d+)";
                    Match matchDouble = Regex.Match(address, patternDouble);
                    if (matchDouble.Success)
                    {
                        int numberDouble = int.Parse(matchDouble.Groups[1].Value);
                        return DataConversion.GetDoubleDataByBytes(bytes, numberDouble);
                    }
                    return null;

                case "float":
                    string patternFloat = @"\.DBD(\d+)";
                    Match matchFloat = Regex.Match(address, patternFloat);
                    if (matchFloat.Success)
                    {
                        int numberFloat = int.Parse(matchFloat.Groups[1].Value);
                        return DataConversion.GetFloatDataByBytes(bytes, numberFloat);
                    }
                    return null;

                case "bool":
                    string patternBool = @"\.DBX(\d+)\.(\d+)";
                    Match matchBool = Regex.Match(address, patternBool);
                    if (matchBool.Success)
                    {
                        int numberBool = int.Parse(matchBool.Groups[1].Value);
                        byte strLength = bytes[numberBool];
                        int lengthBool = int.Parse(matchBool.Groups[2].Value);

                        return DataConversion.GetBit(strLength, lengthBool);
                    }
                    return null;

                default:
                    return null;
            }
        }

        /// <summary>
        /// 读取DB块
        /// </summary>
        /// <param name="db"></param>
        /// <param name="startByteAdr"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public async Task<byte[]> ReadDbBlockAsync(int db, int startByteAdr, int count)
        {
            return await siemenPlc.ReadBytesAsync(DataType.DataBlock, db, startByteAdr, count);
        }

        public object ReadAddress(string address)
        {
            return siemenPlc.Read(address);
        }

        /// <summary>
        /// 读取数据
        /// </summary>
        /// <param name="itemNo">正在循环点位序号</param>
        /// <returns></returns>
        public object ReadValue(int itemNo)
        {
            CommPoint ponit = _ponits.Find(f => f.ItemNo.Equals(itemNo));
            return keyValuePairs[ponit.ItemAddress];
        }

        /// <summary>
        /// 正在循环点位序号集合
        /// </summary>
        /// <param name="itemsNo"></param>
        /// <returns></returns>
        public object ReadValues(int[] itemsNo)
        {
            List<object> list = new List<object>();
            List<CommPoint> ponits = _ponits.FindAll(fa => itemsNo.Contains(fa.ItemNo));
            foreach (var point in ponits)
            {
                list.Add(siemenPlc.ReadAsync(point.ItemAddress).Result);
            }
            return list;
        }

        /// <summary>
        /// 写入数据
        /// </summary>
        /// <param name="itemNo">正在循环点位序号</param>
        /// <param name="value">参数值</param>
        /// <returns></returns>
        public object WriteValue(int itemNo, object value)
        {
            CommPoint ponit = _ponits.Find(f => f.ItemNo.Equals(itemNo));
            return siemenPlc.WriteAsync(ponit.ItemAddress, value);
        }

        /// <summary>
        /// 写入数据
        /// </summary>
        /// <param name="address">绝对地址</param>
        /// <param name="value">参数值</param>
        /// <returns></returns>
        public object WriteAddress(string address, object value)
        {
            return siemenPlc.WriteAsync(address, value);
        }

        /// <summary>
        /// 批量写入
        /// </summary>
        /// <param name="itemsNo">正在循环点位集合</param>
        /// <param name="values">参数集合值(一一对应)</param>
        /// <returns></returns>
        public object WriteValues(int[] itemsNo, string[] values)
        {
            List<bool> list = new List<bool>();
            List<CommPoint> ponits = _ponits.FindAll(fa => itemsNo.Contains(fa.ItemNo));
            for (int i = 0; i < itemsNo.Length; i++)
            {
                bool a = Convert.ToBoolean(siemenPlc.WriteAsync(ponits[i].ItemAddress, values[i]));
                list.Add(a);
            }
            return list;
        }
    }
}